let basicSetting = {
  h1Text: '',
  h1Color: '#333333',
  h1Size: 14,
  h1MarginTop: 3,
  h1MarginBottom: 10,
  h2Size: 10,
  h2Text: '',
  h2Color: '#555555',
  h2MarginTop: 6,
  h2MarginBottom: 10,
  padding: [0, 8, 25, 8],   // top right bottom left
  width: 0, // 500px
  height: 0, // 300px
  background: '#ffffff',  // '#3979cc'
  yAxisFontSize: 9,
  yAxisFontColor: '#888888',
  yAxisLineColor: '#eeeeee',
  yAxisMargin: 5,
  xAxisFontSize: 10,
  xAxisFontColor: '#666666',
  xAxisMargin: 13,
  coordinateSize: 9,
  coordinateColor: '#666666',
  labelSize: 10,
  labelColor: '#333333',
  chartMarginTop: 0,
  color: [
    '#c13d51',
    '#e6c16c',
    '#3676d0',
    '#74DAE5',
    '#394655',
    '#FEE746',
    '#B9A39B',
    '#C18734',
    '#9EC3AD',
    '#6D9BA3',
    '#7E9C82',
    '#DAEE59',
    '#CFDCED'
  ],
  type: 'basic'
}

export class Draw {
  init({ctx, setting, series, xAxis, label}) {
    if (!ctx) {
      throw new Error('without ctx object!')
    }
    this.ctx = ctx
    // check data
    this.checkData(setting, series, xAxis, label)
  }
  checkData(setting = {}, series = [], xAxis = [], label = []) {
    this.setting = Object.assign({}, basicSetting, setting)
    this.series = series.slice(0, 2)
    this.label = label
    this.xAxis = xAxis
  }
  drawLine({width = 1.2, color = '#000000', position = [{x: 0, y: 0}, {x: 0, y: 0}], bezier = 0} = {}) {
    this.ctx.setLineWidth(width)
    this.ctx.setLineCap('round')
    this.ctx.setStrokeStyle(color)
    this.ctx.setGlobalAlpha(1)
    this.ctx.beginPath()
    this.ctx.moveTo(position[0].x, position[0].y, bezier, bezier)
    position.map((item, key) => {
      if (key > 0) {
        if (bezier) {     // draw bezier line
          let ctrlP = this.getBezierPoint(position, key - 1)
          this.ctx.bezierCurveTo(ctrlP.pA.x, ctrlP.pA.y, ctrlP.pB.x, ctrlP.pB.y, item.x, item.y)
        } else {    // draw simple line
          this.ctx.lineTo(item.x, item.y)
        }
      }
    })
    this.ctx.stroke()
    this.ctx.closePath()
  }
  drawDashLine({width = 0.2, color = '#0000000', position = [{x: 0, y: 0}, {x: 0, y: 0}], dashLen = 3}) {
    let x1 = position[0].x
    let x2 = position[1].x
    let y1 = position[0].y
    let y2 = position[1].y
    let beveling = Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2))
    let num = Math.floor(beveling / dashLen)
    this.ctx.setLineWidth(width)
    this.ctx.setLineCap('round')
    this.ctx.setStrokeStyle(color)
    this.ctx.beginPath()
    for (let i = 0; i < num; i++) {
      let x = x1 + (x2 - x1) / num * i
      let y = y1 + (y2 - y1) / num * i
      if (i % 2 === 0) {
        this.ctx.moveTo(x, y)
      } else {
        this.ctx.lineTo(x, y)
      }
    }
    this.ctx.stroke()
    this.ctx.closePath()
  }
  drawPoint({position = {x: 0, y: 0}, size = 1.5, color = '#000000'}) {
    this.ctx.setFillStyle(color)
    this.ctx.beginPath()
    this.ctx.arc(position.x, position.y, size, 0, 2 * Math.PI)
    this.ctx.fill()
    this.ctx.closePath()
  }
  drawText({ text = '', size = 5, color = '#ffffff', align = 'left', position = [0, 0] } = {}) {
    this.ctx.setFillStyle(color)
    this.ctx.setFontSize(size)
    this.ctx.setTextAlign(align)
    this.ctx.setGlobalAlpha(1)
    this.ctx.fillText(text, position[0], position[1])
  }
  drawRect({ color = '#000000', position = [0, 0], width = 0, height = 0, opacity = 0.9 }) {
    this.ctx.beginPath()
    this.ctx.setFillStyle(color)
    this.ctx.setGlobalAlpha(opacity)
    this.ctx.fillRect(position[0], position[1], width, height)
    this.ctx.closePath()
    this.ctx.fill()
  }
  drawArc({ color = '#000000', opacity = 1, position = [0, 0], width = 0 }) {
    this.ctx.beginPath()
    this.ctx.setFillStyle(color)
    this.ctx.setGlobalAlpha(opacity)
    this.ctx.arc(position[0], position[1], width, 0, 2 * Math.PI)
    this.ctx.closePath()
    this.ctx.fill()
  }
  getBezierPoint(ps, i, a, b) {
    let pAx, pAy, pBx, pBy
    if (!a || !b) {
      a = 0.25
      b = 0.25
    }
    // 处理两种极端情形
    if (i < 1) {
      pAx = ps[0].x + (ps[1].x - ps[0].x) * a
      pAy = ps[0].y + (ps[1].y - ps[0].y) * a
    } else {
      pAx = ps[i].x + (ps[i + 1].x - ps[i - 1].x) * a
      pAy = ps[i].y + (ps[i + 1].y - ps[i - 1].y) * a
    }
    if (i > ps.length - 3) {
      let last = ps.length - 1
      pBx = ps[last].x - (ps[last].x - ps[last - 1].x) * b
      pBy = ps[last].y - (ps[last].y - ps[last - 1].y) * b
    } else {
      pBx = ps[i + 1].x - (ps[i + 2].x - ps[i].x) * b
      pBy = ps[i + 1].y - (ps[i + 2].y - ps[i].y) * b
    }
    return {
      pA: {
        x: pAx,
        y: pAy
      },
      pB: {
        x: pBx,
        y: pBy
      }
    }
  }
  getTextLength(text, textSize) {
    var ratio = textSize / 20
    text = text.split('')
    var width = 0
    text.forEach(function (item) {
      if (/[a-zA-Z]/.test(item)) {
        width += (14 * ratio)
      } else if (/[0-9]/.test(item)) {
        width += (11 * ratio)
      } else if (/\./.test(item)) {
        width += (5.4 * ratio)
      } else if (/-/.test(item)) {
        width += (6.5 * ratio)
      } else if (/(\(|\)|%)/.test(item)) {
        width += (12 * ratio)
      } else if (/[\u4e00-\u9fa5]/.test(item)) {
        width += (20 * ratio)
      }
    })
    return parseInt(width)
  }
  isType(obj1, obj2) {
    return Object.prototype.toString.call(obj1) === Object.prototype.toString.call(obj2)
  }
  getData(ob, str) {       // only object
    if (!this.isType(ob, {})) {
      return undefined
    }
    if (!this.isType(str, '')) {
      return undefined
    }
    let value = ob
    let getParams = (obj, arr) => {
      if (this.isType(obj[arr[0]], undefined)) {
        return undefined
      } else {
        if (arr.length > 1) {
          let s = value[arr.shift()]
          return getParams(s, arr)
        } else {
          return obj[arr[0]]
        }
      }
    }
    return getParams(value, str.split('.'))
  }
  upperCaseFirstStr(str) {
    return str.toLowerCase().replace(/\b(\w)|\s(\w)/g, m => m.toUpperCase())
  }
  dateTranform(str) {
    let arr = str.split('/').reverse()
    arr[0] = '20' + arr[0]
    return arr.join('-')
  }
}

export class DrawChart extends Draw {
  beginDraw() {
    // get basic size
    this.getBasicSize()
    // draw background
    this.drawBackground()
    // draw content
    // this.drawContent()
    // draw chart background
    // this.drawChartBackground()
    // draw xAxis yAxis
    this.drawXYAxis()
    this.drawCoordinate()
    this.drawLabel()
    // draw Title
    this.drawTitle()
    // draw chart
    this.drawChart()
    this.ctx.draw()
  }
  getBasicSize() {
    if (this.isType(this.label, []) && this.label.length > 0) {
      this.setting.padding[2] = this.setting.padding[2] + this.setting.labelSize * 2
    }
    // page
    this.page = {
      width: this.setting.width,
      height: this.setting.height,
      position: [[0, 0], [this.setting.width, 0], [this.setting.width, this.setting.height], [0, this.setting.height]]
    }
    // content
    this.content = {
      width: this.setting.width - this.setting.padding[1] - this.setting.padding[3],
      height: this.setting.height - this.setting.padding[0] - this.setting.padding[2],
      position: [
        [this.setting.padding[3], this.setting.padding[0]],
        [this.setting.width - this.setting.padding[1], this.setting.padding[0]],
        [this.setting.width - this.setting.padding[1], this.setting.height - this.setting.padding[2]],
        [this.setting.padding[3], this.setting.height - this.setting.padding[2]]
      ]
    }
    // chart
    let position = [
      [
        this.content.position[0][0] + this.getTextLength(this.series[0].data[0].toString(), this.setting.xAxisFontSize) + this.setting.yAxisMargin,
        this.content.position[0][1] + this.setting.chartMarginTop + (this.setting.h1Text ? this.setting.h1Size + this.setting.h1MarginTop + this.setting.h1MarginBottom : 0) + (this.setting.h2Text ? this.setting.h2Size : 0)
      ],
      [
        this.content.position[1][0] - (this.series.length > 1 ? this.getTextLength(this.series[1].data[0].toString(), this.setting.xAxisFontSize) + this.setting.yAxisMargin : 0),
        this.content.position[0][1] + this.setting.chartMarginTop + (this.setting.h1Text ? this.setting.h1Size + this.setting.h1MarginTop + this.setting.h1MarginBottom : 0) + (this.setting.h2Text ? this.setting.h2Size : 0)
      ],
      [
        this.content.position[1][0] - (this.series.length > 1 ? this.getTextLength(this.series[1].data[0].toString(), this.setting.xAxisFontSize) + this.setting.yAxisMargin : 0),
        this.content.position[2][1] - (this.xAxis ? this.setting.yAxisFontSize : 0)
      ],
      [
        this.content.position[0][0] + this.getTextLength(this.series[0].data[0].toString(), this.setting.xAxisFontSize) + this.setting.yAxisMargin,
        this.content.position[2][1] - (this.xAxis ? this.setting.yAxisFontSize : 0)
      ]
    ]
    this.chart = {
      width: position[1][0] - position[0][0],
      height: position[2][1] - position[1][1],
      position
    }
    // if has parent, align the parent
    if (this.getData(this, 'setting.parent')) {
      const { page, content, chart } = this.getData(this, 'setting.parent') || {}
      const assign = (key, data) => {
        if (data) {
          this[key].width = data.width
          this[key].position = data.position.map((item, k) => [
            item[0],
            this[key].position[k][1]
          ])
        }
      }
      assign('page', page)
      assign('content', content)
      assign('chart', chart)
    }
    // xyAxis
    this.xyAxis = this.series.map((item) => {
      let { data } = item
      if (data && this.isType(data, []) && data.length > 0) {
        return {
          arr: this.getYAxis({arr: data, min: this.getData(item, 'setting.min'), max: this.getData(item, 'setting.max')}),
          scale: 1
        }
      }
      // return util.calculateY(min, max, 5)
    })
  }
  drawBackground() {
    this.ctx.setFillStyle(this.setting.background)
    this.ctx.fillRect(0, 0, this.setting.width, this.setting.height)
  }
  drawContent() {
    this.ctx.setFillStyle('#f0f0f0')
    this.ctx.fillRect(this.content.position[0][0], this.content.position[0][1], this.content.width, this.content.height)
  }
  drawChartBackground() {
    this.ctx.setFillStyle('#dddddd')
    this.ctx.fillRect(this.chart.position[0][0], this.chart.position[0][1], this.chart.width, this.chart.height)
  }
  drawTitle() {
    if (this.setting.h1Text) {
      this.drawText({
        color: this.setting.h1Color,
        size: this.setting.h1Size,
        position: [
          this.content.position[0][0],
          this.content.position[0][1] + this.setting.h1Size + this.setting.h1MarginTop
        ],
        text: this.setting.h1Text,
        align: 'left'
      })
    }
    if (this.setting.h2Text) {
      this.drawText({
        color: this.setting.h2Color,
        size: this.setting.h2Size,
        position: [
          this.content.position[1][0],
          this.content.position[0][1] + this.setting.h2Size + this.setting.h2MarginTop
        ],
        text: this.setting.h2Text,
        align: 'right'
      })
    }
  }
  drawCoordinate() {
    this.series.map((item, key) => {
      if (item.name) {
        // draw yAxis text
        this.drawText({
          color: item.coordinateColor || this.setting.coordinateColor,
          size: this.setting.coordinateSize,
          position: [
            this.chart.position[key][0] - (key === 0 ? 0.5 * this.getTextLength(item.name, this.setting.coordinateSize) : -this.getTextLength(item.name, this.setting.coordinateSize) * 0.5),
            this.chart.position[0][1] - this.setting.coordinateSize * 1.2
          ],
          text: item.name,
          align: ['left', 'right'][key]
        })
      }
    })
  }
  drawLabel() {
    this.isType(this.label, []) && this.label.map(({ text, color }, key) => {
      this.drawRect({
        color,
        position: [
          this.chart.position[3][0] + key * this.chart.width / (this.label.length - 1) - (key === 0 ? 0 : (key === this.label.length - 1 ? this.getTextLength(text, this.setting.labelSize) : 0.5 * this.getTextLength(text, this.setting.labelSize))) - 12,
          this.chart.position[3][1] + this.setting.labelSize
        ],
        width: 9,
        height: 9
      })
      this.drawText({
        color: this.setting.labelColor,
        size: this.setting.labelSize,
        align: key === 0 ? 'left' : (key === this.label.length - 1 ? 'right' : 'center'),
        position: [
          this.chart.position[3][0] + key * this.chart.width / (this.label.length - 1),
          this.chart.position[3][1] + 1.8 * this.setting.labelSize
        ],
        text
      })
    })
  }
  drawXYAxis() {
    // first,draw x solid line
    let singleYAxisHeight = this.chart.height / (this.xyAxis[0].arr.length - 1)
    this.xyAxis.map((axis, axisKey) => {
      axis.arr.map((item, key) => {
        if (axisKey === 0) {
          // draw coordinate line
          let fn = this.drawLine.bind(this)
          fn({
            color: this.setting.yAxisLineColor,
            width: 0.5,
            position: [
              {
                x: this.chart.position[3][0],
                y: this.chart.position[3][1] - singleYAxisHeight * key
              },
              {
                x: this.chart.position[2][0],
                y: this.chart.position[2][1] - singleYAxisHeight * key
              }
            ]
          })
        }
        let textLen = this.getTextLength(item, this.setting.yAxisFontSize)
        // draw coordinate text
        this.drawText({
          color: this.setting.yAxisFontColor,
          size: this.setting.yAxisFontSize,
          position: [
            this.chart.position[3][0] - textLen - this.setting.yAxisMargin + (axisKey === 1 ? (this.chart.width + textLen + 2 * this.setting.yAxisMargin) : 0),
            this.chart.position[3][1] - singleYAxisHeight * key + 0.3 * this.setting.yAxisFontSize
          ],
          text: item
        })
      })
    })
    // draw x axis
    if (this.xAxis && this.xAxis.length > 0) {
      this.xAxis.map((item, key) => {
        let space = this.content.width / (this.xAxis.length - 1)
        this.drawText({
          color: this.setting.xAxisFontColor,
          size: this.setting.xAxisFontSize,
          position: [
            this.content.position[3][0] + space * key,
            this.content.position[3][1] + this.setting.xAxisMargin
          ],
          text: item,
          align: key === 0 ? 'left' : (key === this.xAxis.length - 1 ? 'right' : 'center')
        })
      })
    }
  }
  drawChart() {
    this.series.map((item, key) => {
      let { category, data, isFill } = item
      if (category && data && data.length > 0) {
        let fn = this.getData(this, `draw${this.upperCaseFirstStr(category)}Chart`).bind(this)
        // console.log(fn)
        if (this.isType(fn, () => {})) {
          let { points } = fn({data, color: item.color || this.setting.color[key], isFill: !!isFill, hasPoint: false, bezier: 0, pointSize: 0, width: 1, xyAxis: this.xyAxis[key]})
          item.points = points
        } else {
          console.error(`this.draw${this.upperCaseFirstStr(category)}Chart not a Function`)
        }
      }
    })
  }
  drawLineChart({data = [], color = '#000000', isFill = false, fillColor = '', pointSize = 0, bezier = 0, width = 1, xyAxis = {arr: [], scale: 1}}) {
    let barWidth = this.chart.width / data.length
    let points = data.map((item, key) => {
      let x = this.chart.position[0][0] + key * barWidth + 0.5 * barWidth
      let y = this.getPointPositionY({xyAxis, item})
      // draw point
      if (pointSize) {
        this.drawPoint({position: {x, y}, size: pointSize, color})
      }
      return {x, y}
    })
    // draw line
    this.drawLine({position: points, color, bezier})
    // draw fill background
    if (isFill) {
      fillColor = fillColor || color
      let chartMinPosition = Math.min.apply(null, [this.getPointPositionY({xyAxis}), this.chart.position[3][1]])
      this.ctx.moveTo(points[points.length - 1].x, points[points.length - 1].y)
      this.ctx.lineTo(points[points.length - 1].x, chartMinPosition)
      this.ctx.lineTo(points[0].x, chartMinPosition)
      this.ctx.lineTo(points[0].x, points[0].y)
      this.ctx.setGlobalAlpha(0.2)
      this.ctx.setFillStyle(fillColor)
      this.ctx.fill()
    }
    return { points }
  }
  drawBarChart({data = [], color = '#000000', isFill = true, fillColor = '', xyAxis = {arr: [], scale: 1}}) {
    fillColor = fillColor || color
    let barWidth = this.chart.width / data.length
    let points = data.map((item, key) => {
      let x = this.chart.position[0][0] + key * barWidth + 0.1 * barWidth
      let y = this.getPointPositionY({xyAxis, item})
      let chartMinPosition = Math.min.apply(null, [this.getPointPositionY({xyAxis}), this.chart.position[3][1]])
      let height = chartMinPosition - this.getPointPositionY({xyAxis, item})
      // andriod
      if (height < 0) {
        height = Math.abs(height)
        y = y - height
      }
      // draw bar
      this.drawRect({
        color: fillColor,
        position: [x, y],
        width: Math.abs(0.8 * barWidth),
        height
      })
      return { x, y }
    })
    return { points }
  }
  getPointPositionY({xyAxis = {arr: [], scale: 1}, item = 0}) {
    let sum = parseFloat(xyAxis.arr[xyAxis.arr.length - 1]) - parseFloat(xyAxis.arr[0])
    let dSum = parseFloat(item / parseFloat(xyAxis.scale)) - parseFloat(xyAxis.arr[0])
    let height = (dSum / sum) * this.chart.height
    // console.log(sum, dSum, height)
    return this.chart.position[3][1] - height
  }
  getYAxis({arr = [], min, max, num = 4}) {
    min = parseFloat(this.isType(min, undefined) ? Math.min.apply(null, arr) : min)
    max = parseFloat(this.isType(max, undefined) ? Math.max.apply(null, arr) : max)
    let minAbs = Math.abs(min)
    let maxAbs = Math.abs(max)
    let bettwen = Math.abs((max - min) * 0.1)
    if (min < 0) {
      let MIN = min
      let MAX = max
      min = (minAbs > maxAbs ? MIN : -MAX) - bettwen * 0.5
      max = (maxAbs > minAbs ? MAX : -MIN) + bettwen * 0.5
    } else {
      min = min - bettwen
      max = max + bettwen
    }
    if (!(this.isType(arr, []) && arr.length > 0)) throw new Error('getYAxis Error: arr must be an array, at least one child!')
    if (!this.isType(min, 1) || !this.isType(max, 1) || min > max) throw new Error('getYAxis Error: min | max must be a number,and min must small than max!')
    // let stage = 1.1 * (max - min) / (num) // 预留20%的顶部空白
    let stage = (max - min) / num // 预留20%的顶部空白
    let fixed = stage < 0.001 ? 4 : (stage < 0.01 ? 3 : (stage < 0.1 ? 2 : stage < 2 ? 1 : 0))
    let returnArr = []
    for (let i = 0; i < num + 1; i++) {
      // let data = min + i * stage - (!i ? fixed / num : 0)
      let data = min + i * stage
      returnArr.push(data.toFixed(fixed))
    }
    console.log(stage, returnArr)
    return returnArr
  }
}

export class DrawTip extends Draw {
  beginDraw({ tipPoint, tipLine, resouce }) {
    this.tipPoint = tipPoint.map(({points, color}, key) => ({
      point: points,
      start: points[0].x,
      end: points[points.length - 1].x,
      step: (points[points.length - 1].x - points[0].x) / points.length,
      color
    }))
    this.tipLine = tipLine
    this.resouce = resouce
    this.getBasicSize()
    this.ctx.draw()
  }
  update({positionLeft}) {
    this.ctx.clearRect(this.page.position[0][0], this.page.position[0][1], this.page.position[2][0], this.page.position[2][1])
    let index = 0
    // draw point
    this.tipPoint.map(({ point, start, end, step, color }) => {
      index = Math.round((positionLeft - start) / step)
      this.drawArc({
        color,
        position: [point[index].x, point[index].y],
        width: 6,
        height: 6,
        opacity: 0.4
      })
    })

    // draw tip line
    this.drawTipLine({
      positionLeft: this.tipPoint[0].point[index].x
    })

    // draw tip card
    this.drawTipCard({
      x: this.tipPoint[0].point[index].x,
      y: this.tipPoint[0].point[index].y,
      index
    })
    this.ctx.draw()
  }
  drawTipLine({ positionLeft = 0 } = {}) {
    this.drawDashLine({
      width: 0.1,
      color: '#000000',
      position: [
        {
          x: this.page.position[0][0] + positionLeft,
          y: this.tipLine.top
        },
        {
          x: this.page.position[0][0] + positionLeft,
          y: this.tipLine.bottom
        }
      ]
    })
  }
  drawTipCard({ x = 0, y = 0, index = 0 } = {}) {
    console.log(index, this.resouce[index])
    let tipTextArr = ['trading_date', 'close', 'position_rate', 'change_volume']
    let tipTextConf = ['', '当日收盘价: ', '持股比例: ', '净变动股数: ']
    let tipTextPositionLeft = this.page.position[0][0] + x - 35
    tipTextPositionLeft = Math.max.apply(null, [tipTextPositionLeft, this.page.position[0][0] - 140])
    tipTextPositionLeft = Math.min.apply(null, [tipTextPositionLeft, this.page.position[1][0] - 140])
    this.drawRect({
      color: '#ffffff',
      position: [
        tipTextPositionLeft - 5,
        y + 30
      ],
      width: 130,
      height: 70,
      opacity: 0.8
    })
    tipTextArr.map((item, key) => {
      this.drawText({
        color: '#000000',
        size: 10,
        position: [
          tipTextPositionLeft,
          y + 45 + key * 15
        ],
        text: `${tipTextConf[key]}${item === 'trading_date' ? this.dateTranform(this.resouce[index][item]) : this.resouce[index][item]}`,
        align: 'left'
      })
    })
  }
  getBasicSize() {
    // page
    this.page = {
      width: this.setting.width,
      height: this.setting.height,
      position: [[0, 0], [this.setting.width, 0], [this.setting.width, this.setting.height], [0, this.setting.height]]
    }
  }
}
